﻿using RSSReader.Dto;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.ServiceModel.Syndication;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Xml;

namespace RSSReader
{
    public class ObservableSyndicationFeed : NotificationObject
    {
        //private static readonly string loadingFeedTitle = "Loading...";
        private Uri baseUri;
        private Dictionary<string, ObservableSyndicationItem> itemsDictionary;
        private ObservableCollection<ObservableSyndicationItem> hiddenItems;
        private ReadOnlyObservableCollection<ObservableSyndicationItem> items;
        private DateTimeOffset lastUpdatedTime;
        private string title;

        //Creates the lock object somewhere
        private static object _lock = new object();

        private ObservableSyndicationFeed()
        {
        }

        public static ObservableSyndicationFeed Create(ObservableSyndicationFeedDto feedDto)
        {
            var observableSyndicationFeed = new ObservableSyndicationFeed();

            observableSyndicationFeed.baseUri = feedDto.BaseUri;
            observableSyndicationFeed.itemsDictionary = new Dictionary<string, ObservableSyndicationItem>(feedDto.ItemsDictionary.Count());
            observableSyndicationFeed.hiddenItems = new ObservableCollection<ObservableSyndicationItem>();
            observableSyndicationFeed.items = new ReadOnlyObservableCollection<ObservableSyndicationItem>(observableSyndicationFeed.hiddenItems);
            foreach (var item in feedDto.ItemsDictionary)
            {
                var observableItem = new ObservableSyndicationItem(observableSyndicationFeed, item.Value);
                observableSyndicationFeed.itemsDictionary.Add(item.Key, observableItem);
                observableSyndicationFeed.hiddenItems.Add(observableItem);
            }
            observableSyndicationFeed.lastUpdatedTime = feedDto.LastUpdatedTime;
            observableSyndicationFeed.title = feedDto.Title;

            BindingOperations.EnableCollectionSynchronization(observableSyndicationFeed.items, _lock);

            return observableSyndicationFeed;
        }

        public static ObservableSyndicationFeed Create(Uri uri)
        {
            var syndicationFeed = Load(uri);
            var observableSyndicationFeed = new ObservableSyndicationFeed();

            observableSyndicationFeed.baseUri = syndicationFeed.BaseUri;
            observableSyndicationFeed.itemsDictionary = new Dictionary<string, ObservableSyndicationItem>(syndicationFeed.Items.Count());
            observableSyndicationFeed.hiddenItems = new ObservableCollection<ObservableSyndicationItem>();
            observableSyndicationFeed.items = new ReadOnlyObservableCollection<ObservableSyndicationItem>(observableSyndicationFeed.hiddenItems);
            foreach (var item in syndicationFeed.Items)
            {
                var observableItem = new ObservableSyndicationItem(observableSyndicationFeed, item);
                observableSyndicationFeed.itemsDictionary.Add(item.Id, observableItem);
                observableSyndicationFeed.hiddenItems.Add(observableItem);
            }
            observableSyndicationFeed.lastUpdatedTime = syndicationFeed.LastUpdatedTime;
            observableSyndicationFeed.title = syndicationFeed.Title.Text;

            BindingOperations.EnableCollectionSynchronization(observableSyndicationFeed.items, _lock);

            return observableSyndicationFeed;
        }

        public static async Task<ObservableSyndicationFeed> CreateAsync(Uri uri)
        {
            var syndicationFeed = await LoadAsync(uri);
            var observableSyndicationFeed = new ObservableSyndicationFeed();

            observableSyndicationFeed.baseUri = syndicationFeed.BaseUri;
            observableSyndicationFeed.itemsDictionary = new Dictionary<string, ObservableSyndicationItem>(syndicationFeed.Items.Count());
            observableSyndicationFeed.hiddenItems = new ObservableCollection<ObservableSyndicationItem>();
            observableSyndicationFeed.items = new ReadOnlyObservableCollection<ObservableSyndicationItem>(observableSyndicationFeed.hiddenItems);
            foreach (var item in syndicationFeed.Items)
            {
                var observableItem = new ObservableSyndicationItem(observableSyndicationFeed, item);
                observableSyndicationFeed.itemsDictionary.Add(item.Id, observableItem);
                observableSyndicationFeed.hiddenItems.Add(observableItem);
            }
            observableSyndicationFeed.lastUpdatedTime = syndicationFeed.LastUpdatedTime;
            observableSyndicationFeed.title = syndicationFeed.Title.Text;

            BindingOperations.EnableCollectionSynchronization(observableSyndicationFeed.items, _lock);

            return observableSyndicationFeed;
        }

        private void SharedCreate()
        { 
            
        }

        public Uri BaseUri
        {
            get
            {
                return this.baseUri;
            }
            set
            {
                if (this.baseUri != value)
                {
                    this.baseUri = value;
                    this.OnPropertyChanged();
                }
            }
        }

        public ReadOnlyObservableCollection<ObservableSyndicationItem> Items
        {
            get
            {
                return this.items;
            }
        }

        public DateTimeOffset LastUpdatedTime
        {
            get
            {
                return this.lastUpdatedTime;
            }
            set
            {
                if (this.lastUpdatedTime != value)
                {
                    this.lastUpdatedTime = value;
                    this.OnPropertyChanged();
                }
            }
        }

        public string Title
        {
            get
            {
                return this.title;
            }
            set
            {
                if (this.title != value)
                {
                    this.title = value;
                    this.OnPropertyChanged();
                }
            }
        }

        public void Update()
        {
            var updatedSyndicationFeed = Load(this.BaseUri);

            //Feed must be newer then the feed present
            if (this.LastUpdatedTime == updatedSyndicationFeed.LastUpdatedTime)
                return;

            this.BaseUri = updatedSyndicationFeed.BaseUri;
            this.LastUpdatedTime = updatedSyndicationFeed.LastUpdatedTime;
            this.Title = updatedSyndicationFeed.Title.Text;

            this.UpdateItems(updatedSyndicationFeed.Items);
        }

        public async Task UpdateAsync()
        {
            var updatedSyndicationFeed = await LoadAsync(this.BaseUri);

            //Feed must be newer then the feed present
            if (this.LastUpdatedTime == updatedSyndicationFeed.LastUpdatedTime)
                return;

            this.BaseUri = updatedSyndicationFeed.BaseUri;
            this.LastUpdatedTime = updatedSyndicationFeed.LastUpdatedTime;
            this.Title = updatedSyndicationFeed.Title.Text;

            this.UpdateItems(updatedSyndicationFeed.Items);
        }

        private void UpdateItems(IEnumerable<SyndicationItem> updatedItems)
        {
            foreach (var item in updatedItems)
            {
                //check if item is already present
                if (this.itemsDictionary.ContainsKey(item.Id))
                {
                    //update the item
                    this.itemsDictionary[item.Id].Update(item);
                }
                else
                {
                    var observableItem = new ObservableSyndicationItem(this, item);
                    this.itemsDictionary.Add(item.Id, observableItem);
                    this.hiddenItems.Add(observableItem);
                }
            }
        }

        private static SyndicationFeed Load(Uri uri)
        {
            SyndicationFeed feed;

            WebClient client = new WebClient();
            var feedStream = client.OpenRead(uri.AbsoluteUri);

            using (XmlReader xmlReader = XmlReader.Create(feedStream))
            {
                feed = SyndicationFeed.Load(xmlReader);
            }

            if (feed.BaseUri == null)
            {
                feed.BaseUri = uri;
            }

            return feed;
        }

        private static async Task<SyndicationFeed> LoadAsync(Uri uri)
        {
            SyndicationFeed feed;

            var client = new HttpClient();
            var feedStream = await client.GetStreamAsync(uri.AbsoluteUri);

            XmlReader xmlReader = XmlReader.Create(feedStream);

            feed = SyndicationFeed.Load(xmlReader);

            if (feed.BaseUri == null)
            {
                feed.BaseUri = uri;
            }

            return feed;            
        }

        public void ShowBaseUriInBrowser()
        {
            if (this.baseUri == null)
            {
                throw new Exception("Feed has no BaseUri");
            }

            if (!this.baseUri.IsWellFormedOriginalString())
            {
                throw new Exception("Feed has no valid URL");
            }

            if (!(this.baseUri.Host.StartsWith("http://") || this.baseUri.Host.StartsWith("https://")))
            {
                Process.Start("http://" + this.baseUri.Host);
            }
            else
            {
                Process.Start(this.BaseUri.Host);
            }
            
        }

        public ObservableSyndicationFeedDto GetDto()
        {
            var feedDto = new ObservableSyndicationFeedDto();

            feedDto.BaseUri = this.BaseUri;
            feedDto.Items = new List<ObservableSyndicationItemDto>(this.hiddenItems.Count);
            foreach (var item in this.hiddenItems)
            {
                feedDto.Items.Add(item.GetDto());
            }
            feedDto.ItemsDictionary = new Dictionary<string, ObservableSyndicationItemDto>();
            foreach (var item in this.itemsDictionary)
            {
                feedDto.ItemsDictionary.Add(item.Key, item.Value.GetDto());
            }
            feedDto.LastUpdatedTime = this.LastUpdatedTime;
            feedDto.Title = this.Title;
            
            return feedDto;
        }
    }
}
